package com.ruoyi.web.controller.system;

import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

import com.ruoyi.system.domain.VideoData;
import com.ruoyi.system.mapper.VideoDataMapper;
import com.ruoyi.system.mapper.VideoTaskMapper;
import com.ruoyi.web.controller.utils.FileTypeCheckerUtil;
import org.apache.shiro.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.service.IVideoSaveService;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Controller
@RequestMapping("/system/videoSave")
public class VideoSaveController extends BaseController {

    @Value("${ffmpeg.savePath}")
    private String localVideoDir;

    @Autowired
    private IVideoSaveService videoSaveService;
    @Autowired
    private VideoDataMapper videoDataMapper;


    @Log(title = "视频流采集", businessType = BusinessType.INSERT)
    @PostMapping("/startSave")
    @ResponseBody
    public AjaxResult startSave(String streamIds,Integer collectionTaskId,String collectionTaskName) {
        AjaxResult ajaxResult = videoSaveService.startSave(streamIds,collectionTaskId,collectionTaskName);
        return ajaxResult;
    }

    @Log(title = "视频流采集", businessType = BusinessType.INSERT)
    @PostMapping("/stopSave")
    @ResponseBody
    public AjaxResult stopSave(Long dataId) {
        AjaxResult ajaxResult = videoSaveService.stopSave(dataId);
        return ajaxResult;
    }

    @Log(title = "视频流采集", businessType = BusinessType.INSERT)
    @PostMapping("/getStatus")
    @ResponseBody
    public AjaxResult getStatus() {
        Set<String> finalizingTasks = videoSaveService.getFinalizationTaskIds();
        AjaxResult ajaxResult = AjaxResult.success(finalizingTasks);
        return ajaxResult;
    }

    @Log(title = "视频流采集", businessType = BusinessType.INSERT)
    @PostMapping("/getFinalizationStatus")
    @ResponseBody
    public AjaxResult getFinalizationStatus(String threadID) {
        AjaxResult ajaxResult = videoSaveService.getFinalizationStatus(threadID);
        return ajaxResult;
    }

    @Log(title = "视频流采集", businessType = BusinessType.INSERT)
    @PostMapping("/getBatchFinalizationStatus")
    @ResponseBody
    public AjaxResult getBatchFinalizationStatus() {
        Set<String> finalizingTasks = videoSaveService.getFinalizationTaskIds();
        AjaxResult ajaxResult = videoSaveService.getBatchFinalizationStatus(new ArrayList<>(finalizingTasks));
        return ajaxResult;
    }

    @GetMapping("/deleteData")
    @ResponseBody
    public AjaxResult deleteData(Long dataId, String telemetryPath) {
        // 1. 基础参数校验（新增 telemetryPath 非空校验，避免无效路径）
        if (dataId == null) {
            return AjaxResult.error("参数错误：dataId不能为空");
        }
        if (!StringUtils.hasText(telemetryPath)) {
            return AjaxResult.error("参数错误：telemetryPath不能为空（需传入遥测路径相对目录）");
        }

        // 2. 查询视频数据（校验存在性）
        VideoData videoData = videoDataMapper.selectVideoDataByDataId(dataId);
        if (videoData == null) {
            return AjaxResult.error("数据不存在：dataId=" + dataId);
        }

        // 3. 路径处理：统一格式（仅处理视频存储路径，遥测路径用入参）
        String storagePath = videoData.getStoragePath();
        System.err.println("/storagePath-------" + storagePath);
        String normalizedStoragePath = "";

        // 处理视频存储路径（保持原逻辑，确保格式正确）
        if (StringUtils.hasText(storagePath)) {
            // 统一分隔符为系统原生（Windows=\，Linux=/）
            normalizedStoragePath = storagePath.replace("\\", File.separator)
                    .replace("/", File.separator);
            // 去除开头多余的分隔符，避免拼接后出现 "D:/video/save//2025-10-10" 格式
            if (normalizedStoragePath.startsWith(File.separator)) {
                normalizedStoragePath = normalizedStoragePath.substring(1);
            }
        } else {
            // 若视频存储路径为空，直接返回错误（避免删除根目录等危险操作）
            return AjaxResult.error("视频存储路径为空：dataId=" + dataId);
        }

        // 4. 构建遥测路径（核心调整：使用入参 telemetryPath 拼接，而非 lastLevelDir）
        // 先统一 telemetryPath 的分隔符，避免混合分隔符导致路径错误
        String normalizedTelemetryPath = telemetryPath.replace("\\", File.separator)
                .replace("/", File.separator);
        // 拼接遥测根目录 "telemetryStoragePath/" + 入参路径（用系统分隔符替代硬编码 /）
        String telemetryStoragePath = "telemetryStoragePath" + File.separator + normalizedTelemetryPath;
        // 去除路径中可能的重复分隔符（如 telemetryPath 开头带 / 导致的 "telemetryStoragePath//xxx"）
        telemetryStoragePath = telemetryStoragePath.replace(File.separator + File.separator, File.separator);

        // 5. 构建绝对路径（用 Path.resolve 确保跨平台兼容性，避免拼接错误）
        Path baseDirPath = Paths.get(localVideoDir);
        // 视频存储绝对路径
        Path absoluteStoragePathObj = baseDirPath.resolve(normalizedStoragePath);
        String absoluteStoragePath = absoluteStoragePathObj.toString();
        // 遥测存储绝对路径（核心调整：基于入参构建的路径）
        Path absoluteTelemetryPathObj = baseDirPath.resolve(telemetryStoragePath);
        String absoluteTelemetryStoragePath = absoluteTelemetryPathObj.toString();

        // 调试输出：确认路径格式（重点检查遥测路径是否符合预期）
        System.err.println("normalizedStoragePath--------" + normalizedStoragePath);
        System.err.println("telemetryStoragePath（相对路径）--------" + telemetryStoragePath);
        System.err.println("absoluteStoragePath（视频绝对路径）--------" + absoluteStoragePath);
        System.err.println("absoluteTelemetryStoragePath（遥测绝对路径）--------" + absoluteTelemetryStoragePath);

        // 6. 执行删除（处理占用问题，保持原逻辑）
        List<String> deleteFailFiles = new ArrayList<>();
        // 删除视频目录（含内容+目录本身）
        if (StringUtils.hasText(absoluteStoragePath)) {
            deleteDirWithForceRelease(new File(absoluteStoragePath), deleteFailFiles);
        }
        // 删除遥测目录（含内容+目录本身，基于入参路径）
        if (StringUtils.hasText(absoluteTelemetryStoragePath)) {
            deleteDirWithForceRelease(new File(absoluteTelemetryStoragePath), deleteFailFiles);
        }

        // 7. 删除数据库记录（保持原逻辑）
        int deleteCount = videoDataMapper.deleteVideoDataByDataId(dataId);
        if (deleteCount <= 0) {
            return AjaxResult.error("数据库记录删除失败");
        }

        // 8. 结果反馈（保持原逻辑，明确失败路径）
        if (!deleteFailFiles.isEmpty()) {
            return AjaxResult.success("操作完成，但部分文件/目录删除失败",
                    "失败列表：" + String.join(";", deleteFailFiles));
        }
        return AjaxResult.success("操作成功！");
    }

    /**
     * 核心删除方法：强制删除目录（含内容），JDK 8 兼容
     * @param target 目标文件/目录
     * @param failList 失败记录列表
     */
    private void deleteDirWithForceRelease(File target, List<String> failList) {
        if (target == null || !target.exists()) {
            failList.add("目标不存在：" + (target != null ? target.getAbsolutePath() : "null"));
            return;
        }

        // 处理文件：重试删除（应对占用）
        if (target.isFile()) {
            boolean deleted = forceDeleteFile(target);
            if (deleted) {
                System.err.println("文件删除成功：" + target.getAbsolutePath());
            } else {
                failList.add("文件删除失败（已重试3次）：" + target.getAbsolutePath());
            }
            return;
        }

        // 处理目录：先删子内容，再删目录
        File[] childFiles = target.listFiles();
        if (childFiles == null) {
            failList.add("无法读取目录子内容（权限不足）：" + target.getAbsolutePath());
            return;
        }
        // 递归删除所有子内容
        for (File child : childFiles) {
            deleteDirWithForceRelease(child, failList);
        }

        // 删除空目录（重试）
        boolean dirDeleted = forceDeleteDir(target);
        if (dirDeleted) {
            System.err.println("目录删除成功：" + target.getAbsolutePath());
        } else {
            failList.add("目录删除失败（已重试3次）：" + target.getAbsolutePath());
        }
    }

    /**
     * 强制删除文件（JDK 8 兼容，无 var 关键字）
     * @param file 目标文件
     * @return 是否删除成功
     */
    private boolean forceDeleteFile(File file) {
        // 重试3次，每次间隔1秒
        for (int i = 0; i < 3; i++) {
            try {
                // 释放文件资源（JDK 8 正确关闭流，无 var）
                releaseFileResource(file);
                // 执行删除
                if (file.delete()) {
                    return true;
                }
                // 等待1秒重试
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            } catch (Exception e) {
                continue; // 忽略临时异常
            }
        }
        return false;
    }

    /**
     * 释放文件资源（修复 Files.flush 错误，JDK 8 兼容）
     * @param file 目标文件
     */
    private void releaseFileResource(File file) {
        RandomAccessFile raf = null; // JDK 8 需显式声明类型，不能用 var
        try {
            // 打开文件后立即关闭，强制释放JVM持有的文件句柄
            raf = new RandomAccessFile(file, "r");
            raf.close(); // 显式关闭流
        } catch (IOException e) {
            // 忽略异常：说明文件未被JVM占用（可能是外部进程占用）
        } finally {
            // 确保流一定关闭（避免资源泄漏）
            if (raf != null) {
                try {
                    raf.close();
                } catch (IOException e) {
                    // 忽略关闭异常
                }
            }
        }
    }

    /**
     * 强制删除空目录（重试机制）
     * @param dir 目标目录
     * @return 是否删除成功
     */
    private boolean forceDeleteDir(File dir) {
        // 先确认目录为空
        if (dir.listFiles() != null && dir.listFiles().length > 0) {
            return false;
        }
        // 重试3次
        for (int i = 0; i < 3; i++) {
            try {
                if (dir.delete()) {
                    return true;
                }
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        return false;
    }


    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");

    private static final String EDIT_SUB_DIR = "edit";


    @Autowired
    private VideoTaskMapper videoTaskMapper;

    /**
     * 视频剪辑接口
     * @param inputPath 输入视频的相对路径（相对于 localVideoDir，如 "20250902/菜鸟图库-大城市夜景.mp4"）
     *                 若 inputPath 是绝对路径，可直接使用，无需拼接
     * @param outputPath 前端传入的输出文件名（仅文件名，如 "cut-video.mp4"）
     * @param startTime 剪辑开始时间（格式：秒 或 HH:MM:SS，如 "10" 或 "00:00:10"）
     * @param duration 剪辑时长（格式同上，如 "20" 或 "00:00:20"）
     * @return 0=成功，1=失败
     */
    @GetMapping("/cutVideo")
    @ResponseBody
    public synchronized AjaxResult cutVideo(
            @RequestParam String inputPath,
            @RequestParam String outputPath,
            @RequestParam String startTime,
            @RequestParam String duration) {

        // 1. 处理输入路径：若 inputPath 是相对路径，拼接 localVideoDir 为绝对路径
        File inputFile;
        if (new File(inputPath).isAbsolute()) {
            // 若 inputPath 已是绝对路径（如 "D:/video/save/20250902/xxx.mp4"），直接使用
            inputFile = new File(inputPath);
        } else {
            // 若 inputPath 是相对路径（如 "20250902/xxx.mp4"），拼接根目录
            inputFile = new File(localVideoDir, inputPath);
        }
        // 校验输入文件是否存在
        if (!inputFile.exists() || !inputFile.isFile()) {
            System.err.println("输入文件不存在或不是有效文件：" + inputFile.getAbsolutePath());
            return AjaxResult.error("输入文件不存在或不是有效文件:" + inputFile.getAbsolutePath());
        }


        // 2. 构建输出路径：localVideoDir + /edit/ + 当前日期 + /outputFileName
        // 2.1 获取当前日期（yyyyMMdd 格式）
        String currentDate = DATE_FORMAT.format(new Date());
        // 2.2 拼接完整输出目录（D:\video\save\edit\20250903）
        File outputDir = new File(localVideoDir, EDIT_SUB_DIR + File.separator + currentDate);
        // 2.3 自动创建目录（若父目录不存在，递归创建）
        if (!outputDir.exists()) {
            boolean mkdirsSuccess = outputDir.mkdirs();
            if (!mkdirsSuccess) {
                System.err.println("创建输出目录失败：" + outputDir.getAbsolutePath());
                return AjaxResult.error("创建输出目录失败：" + outputDir.getAbsolutePath());
            }
        }
        // 2.4 拼接最终输出文件绝对路径（D:\video\save\edit\20250903\cut-video.mp4）
        File outputFile = new File(outputDir, outputPath);
        String outputAbsolutePath = outputFile.getAbsolutePath();


        // 3. 构建 FFmpeg 命令（使用绝对路径，避免路径问题）
        String[] command = {
                ffmpegPathConfiguration,
                "-i", inputFile.getAbsolutePath(),  // 输入文件绝对路径
                "-ss", startTime,                  // 开始时间
                "-t", duration,                    // 剪辑时长
                "-c", "copy",                      // 流复制（速度快，同格式推荐）
                "-y",                              // 覆盖已有文件，无需确认
                outputAbsolutePath                 // 输出文件绝对路径
        };

        ProcessBuilder processBuilder = new ProcessBuilder(command);
        // 合并错误流到输出流（统一打印日志，便于排查错误）
        processBuilder.redirectErrorStream(true);

        Process process = null;
        try {
            // 执行 FFmpeg 命令
            process = processBuilder.start();
            // 读取 FFmpeg 输出日志
            InputStream inputStream = process.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); // 避免中文日志乱码
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("FFmpeg输出：" + line);
            }

            // 等待命令执行完成，获取退出码（0=成功，非0=失败）
            int exitCode = process.waitFor();
            if (exitCode == 0) {
                System.out.println("视频剪辑成功！输出路径：" + outputAbsolutePath);
            } else {
                System.err.println("视频剪辑失败！FFmpeg退出码：" + exitCode);
            }
            return AjaxResult.success("操作成功","/file/"+EDIT_SUB_DIR + File.separator + currentDate + "/" +outputPath);

        } catch (IOException e) {
            System.err.println("FFmpeg命令执行异常（如FFmpeg路径错误、文件权限不足）：");
            e.printStackTrace();
            return AjaxResult.error(e.getMessage());
        } catch (InterruptedException e) {
            System.err.println("视频剪辑进程被中断：");
            e.printStackTrace();
            Thread.currentThread().interrupt(); // 恢复中断状态
            return AjaxResult.error(e.getMessage());
        } finally {
            // 确保进程资源释放，避免内存泄漏
            if (process != null && process.isAlive()) {
                process.destroy();
            }
        }
    }


    @Value("${ffmpeg.path}")
    private String ffmpegPathConfiguration;


    /**
     * 合并多个视频文件（支持逗号分隔的路径字符串）
     * @param inputPaths 输入视频路径（英文逗号分隔，如 "20250902/1.mp4,20250902/2.mp4"）
     * @param outputPath 输出视频文件名（如 "merged.mp4"）
     * @param useReencode 是否强制重新编码（解决不同编码格式合并失败问题）
     * @return AjaxResult 包含成功状态和输出路径
     */
    @GetMapping("/mergeVideos")
    @ResponseBody
    public synchronized AjaxResult mergeVideos(
            @RequestParam String inputPaths,
            @RequestParam String outputPath,
            @RequestParam(defaultValue = "false") boolean useReencode) {

        // 1. 解析输入路径（将逗号分隔的字符串转为列表）
        List<String> videoPaths = parseInputPaths(inputPaths);
        // 校验解析结果
        if (videoPaths == null || videoPaths.size() < 2) {
            return AjaxResult.error("输入视频数量不足（至少需要2个视频）");
        }

        // 2. 处理所有输入视频路径（转换为绝对路径并校验）
        List<String> absoluteInputPaths = new ArrayList<>();
        for (String path : videoPaths) {
            File inputFile;
            if (new File(path).isAbsolute()) {
                inputFile = new File(path);
            } else {
                inputFile = new File(localVideoDir, path);
            }
            // 校验输入文件是否存在
            if (!inputFile.exists() || !inputFile.isFile()) {
                String errorMsg = "输入文件不存在或不是有效文件：" + inputFile.getAbsolutePath();
                System.err.println(errorMsg);
                return AjaxResult.error(errorMsg);
            }
            absoluteInputPaths.add(inputFile.getAbsolutePath());
        }

        // 3. 构建输出路径：localVideoDir + /edit/ + 当前日期 + /outputFileName
        // 3.1 获取当前日期（yyyyMMdd 格式）
        String currentDate = DATE_FORMAT.format(new Date());
        // 3.2 拼接完整输出目录（D:\video\save\edit\20250903）
        File outputDir = new File(localVideoDir, EDIT_SUB_DIR + File.separator + currentDate);
        // 3.3 自动创建目录（若父目录不存在，递归创建）
        if (!outputDir.exists()) {
            boolean mkdirsSuccess = outputDir.mkdirs();
            if (!mkdirsSuccess) {
                String errorMsg = "创建输出目录失败：" + outputDir.getAbsolutePath();
                System.err.println(errorMsg);
                return AjaxResult.error(errorMsg);
            }
        }
        // 3.4 拼接最终输出文件绝对路径（D:\video\save\edit\20250903\merged.mp4）
        File outputFile = new File(outputDir, outputPath);
        String outputAbsolutePath = outputFile.getAbsolutePath();

        // 4. 设置FFmpeg路径（与剪辑方法保持一致）
        String ffmpegPath = ffmpegPathConfiguration;

        // 5. 创建临时文件列表（FFmpeg需要通过文件列表指定合并顺序）
        File listFile = null;
        try {
            listFile = createInputListFile(absoluteInputPaths);
            if (listFile == null) {
                return AjaxResult.error("创建临时文件列表失败");
            }

            // 6. 构建合并命令并执行
            String[] command = buildMergeCommand(ffmpegPath, listFile.getAbsolutePath(), outputAbsolutePath, useReencode);
            ProcessBuilder processBuilder = new ProcessBuilder(command);
            processBuilder.redirectErrorStream(true);
            Process process = processBuilder.start();

            // 7. 读取输出日志
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), "UTF-8"))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println("FFmpeg合并输出：" + line);
                }
            }

            // 8. 等待执行完成并处理结果
            int exitCode = process.waitFor();
            if (exitCode == 0) {
                System.out.println("视频合并成功！输出路径：" + outputAbsolutePath);
                // 构造返回的访问路径（与剪辑方法格式一致）
                String accessPath = "/file/" + EDIT_SUB_DIR + File.separator + currentDate + "/" + outputPath;
                return AjaxResult.success("视频合并成功", accessPath);
            } else {
                String errorMsg = "视频合并失败！FFmpeg退出码：" + exitCode;
                System.err.println(errorMsg);
                return AjaxResult.error(errorMsg);
            }

        } catch (IOException e) {
            System.err.println("FFmpeg命令执行异常：");
            e.printStackTrace();
            return AjaxResult.error("视频合并失败：" + e.getMessage());
        } catch (InterruptedException e) {
            System.err.println("视频合并进程被中断：");
            e.printStackTrace();
            Thread.currentThread().interrupt(); // 恢复中断状态
            return AjaxResult.error("视频合并被中断：" + e.getMessage());
        } finally {
            // 删除临时文件（避免残留）
            if (listFile != null && listFile.exists()) {
                if (!listFile.delete()) {
                    System.err.println("临时文件删除失败：" + listFile.getAbsolutePath());
                }
            }
        }
    }

    /**
     * 将逗号分隔的路径字符串解析为列表
     * 处理可能的空格（如路径中包含空格的情况）
     */
    private List<String> parseInputPaths(String inputPaths) {
        if (inputPaths == null || inputPaths.trim().isEmpty()) {
            return null;
        }
        // 按逗号分割，并去除每个路径的前后空格
        String[] pathsArray = inputPaths.split(",");
        for (int i = 0; i < pathsArray.length; i++) {
            pathsArray[i] = pathsArray[i].trim();
        }
        return Arrays.asList(pathsArray);
    }

    /**
     * 创建包含所有输入视频路径的临时文件（FFmpeg合并需要）
     */
    private File createInputListFile(List<String> inputPaths) throws IOException {
        // 创建临时文件，内容格式为：file '视频路径'（兼容路径中的空格和特殊字符）
        File listFile = File.createTempFile("video_list", ".txt");
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(listFile), "UTF-8"))) {
            for (String path : inputPaths) {
                // 处理路径中的单引号（FFmpeg要求用单引号包裹路径时，内部单引号需转义）
                writer.write("file '" + path.replace("'", "'\\''") + "'");
                writer.newLine();
            }
        }
        return listFile;
    }

    /**
     * 构建FFmpeg合并命令
     */
    private String[] buildMergeCommand(String ffmpegPath, String listFilePath,
                                       String outputPath, boolean useReencode) {
        if (useReencode) {
            // 强制重新编码（兼容不同编码格式，速度较慢）
            return new String[]{
                    ffmpegPath,
                    "-f", "concat",         // 使用concat格式
                    "-safe", "0",           // 允许使用绝对路径
                    "-i", listFilePath,     // 输入文件列表
                    "-c:v", "libx264",      // 视频编码器（H.264）
                    "-c:a", "aac",          // 音频编码器
                    "-strict", "experimental", // 允许实验性编码器
                    "-y",                   // 覆盖输出文件
                    outputPath
            };
        } else {
            // 流复制（仅适用于相同编码格式，速度快）
            return new String[]{
                    ffmpegPath,
                    "-f", "concat",         // 使用concat格式
                    "-safe", "0",           // 允许使用绝对路径
                    "-i", listFilePath,     // 输入文件列表
                    "-c", "copy",           // 复制流，不重新编码
                    "-y",                   // 覆盖输出文件
                    outputPath
            };
        }
    }




    @PostMapping("/upload")
    @ResponseBody
    public AjaxResult upload(HttpServletRequest request,
                             @RequestParam("file") MultipartFile file,
                             @RequestParam("path") String path) throws IOException {

        if (!FileTypeCheckerUtil.isAllowedFileType(file)){
            return AjaxResult.error("文件类型错误");
        }
        File filePath;
        if (!file.isEmpty()) {
            // 1. 上传文件路径（原逻辑保留）
            String absolutePath = localVideoDir + path + "/";
            // 2. 获得上传文件的原始文件名（含后缀，如 "test.mp4"）
            String originalFileName = file.getOriginalFilename();
            assert originalFileName != null; // 确保文件名非空

            // 3. 核心修改：获取不带后缀的文件名（替换原date变量的错误赋值）
            int lastDotIndex = originalFileName.lastIndexOf(".");
            String fileNameWithoutSuffix; // 新变量：不带后缀的文件名
            if (lastDotIndex > 0) { // 存在有效后缀（避免处理".gitignore"这类隐藏文件）
                fileNameWithoutSuffix = originalFileName.substring(0, lastDotIndex);
            } else { // 无后缀（如"README"）或隐藏文件（如".env"），直接用原文件名
                fileNameWithoutSuffix = originalFileName;
            }

            // 4. 获取文件后缀名（原逻辑保留，如 ".mp4"）
            String suffix = lastDotIndex > 0 ? originalFileName.substring(lastDotIndex) : "";

            // 5. 构建最终文件路径（用"不带后缀的文件名 + 后缀"，避免原逻辑中文件名重复拼接）
            filePath = new File(absolutePath + fileNameWithoutSuffix + suffix);

            // 6. 若文件目录不存在，创建目录（原逻辑保留）
            if (!filePath.getParentFile().exists()) {
                filePath.getParentFile().mkdirs();
            }

            // 7. 保存文件（原逻辑保留）
            file.transferTo(filePath);

            // 8. 返回结果（访问路径用"不带后缀的文件名 + 后缀"，与存储路径一致）
            return AjaxResult.success("上传成功", "/file" + path + "/" + fileNameWithoutSuffix + suffix);
        } else {
            return AjaxResult.error("上传失败");
        }
    }



}
